﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Tradex.Messaging {

    /// <summary>
    /// The messaging Bus class is the central core object for the messaging system. Multiple busses can coexist, but they are independant from
    /// each other. The bus is mostly used to manage Ports - to create them, destroy them, assign them their addres ID's and register well
    /// known port addresses for service ports (i.e. ports that other parts of the program need to find because they provide access to
    /// services, such as retrieving quotes.
    /// 
    /// In the curent implementation, a Bus is in memory local. There is no concept of routing messages across system boundaries. This does not
    /// mean that it is not possibly to implement that outside of the messaging system. For example, a service port for delivering price feed
    /// information must reside in the same system, but the software behind could connnect through the network and stream those info from
    /// another computer.
    /// </summary>
    public class Bus {

        /// <summary>
        /// The public constructor for creating a new bus.
        /// </summary>
        public Bus() {
            _MessagePorts = new Dictionary<Address, Port>();
            _ServicePorts = new Dictionary<String, Port>();

            // We create the ServicePort which can be used to do all kinds of stuff later on, like registering timers
            // or automatically measure delays on ports (with test messages that are timed)
            Port servicePort = CreatePort();
            RegisterServicePort(servicePort, ServicePortName);
        }

        Dictionary<Address, Port> _MessagePorts;

        Dictionary<String, Port> _ServicePorts;

        UInt32 _LastPortId = 0;

        /// <summary>
        /// This constant defines the known name of the system bus.
        /// </summary>
        public const string ServicePortName = "SYSTEM-BUS";

        /// <summary>
        /// This internal method processes a message. It is called from a port of the bus if it needs to send a message
        /// (for example on behalf of the user of the port).
        /// </summary>
        /// <param name="message"></param>
        internal void SendMessage (Message message) {
            lock (this) {
                if (message.Receiver == Address.LocalBroadcast) {
                    // local broadcast - we copy the message to all ports.
                    foreach (Port port in _MessagePorts.Values) {
                        port.DeliverMessage(message);
                    }
                    return;
                }
                // This is a single shot message... So we send it to the recipient.
                Port recipient = _MessagePorts[message.Receiver];
                recipient.DeliverMessage(message);
            }
        }

        /// <summary>
        /// Creates a port for usage with the MessageBus. A port is the "end terminal" for entering and retrieving messages.
        /// The resulting port is not registered as any service and is (thus) a totally "normal" end user (client) port.
        /// </summary>
        /// <returns></returns>
        public Port CreatePort() {
            Port port = null;
            try {
                lock (this) {
                    if (_LastPortId == UInt32.MaxValue - 1) {
                        throw new InvalidOperationException("Sorry, no more ports - bus address space exhausted.");
                    }
                    Address address = new Address();
                    _LastPortId++;  // This is a workaround. We MAY theoretically run out of ports. May require rework later.
                    address.PortId = _LastPortId;
                    port = new Port(this, address);
                    _MessagePorts[address] = port;
                }
                return port;
            } catch (Exception) {
                if (port != null) {
                    port.Dispose();
                }
                throw;
            }
        }

        /// <summary>
        /// This method registers a ServicePort for a given service code - a string used to identify services. It fails when the
        /// service code is already registered. Unregistering a service is not supported, except by the service through disposing
        /// the port.
        /// </summary>
        /// <param name="port"></param>
        /// <param name="serviceCode"></param>
        public void RegisterServicePort(Port port, string serviceCode) {
            lock (this) {
                if (_ServicePorts.Keys.Contains (serviceCode)) {
                    throw new ArgumentException ("Service Code already registered", "serviceCode");
                }
                _ServicePorts[serviceCode] = port;
            }
        }

        /// <summary>
        /// This method  unregisters a service port.
        /// </summary>
        /// <param name="port"></param>
        /// <param name="serviceCode"></param>
        public void UnregisterServicePort(Port port, string serviceCode) {
            lock (this) {
                if (!_ServicePorts.Keys.Contains(serviceCode)) {
                    throw new ArgumentException("Service Code not registered", "serviceCode");
                }
                Port registeredPort;
                if (!_ServicePorts.TryGetValue(serviceCode, out registeredPort)) {
                }
                if (port != registeredPort) {
                    throw new ArgumentException("wrong port - another port is registered here", "port");
                }
                _ServicePorts.Remove(serviceCode);
            }
        }

        internal void UnregisterPort(Port port) {
            lock (this) {
                _MessagePorts.Remove(port.Address);
            }
        }

    }

}
